#include <boost/asio.hpp>

#include <iostream>
#include <set>
#include <string>
#include <memory>

/**
 * 1 同步读写的缺陷在于读写是阻塞的，如果客户端对端不发送数据服务器的read操作是阻塞的，这将导致服务器处于阻塞等待状态。
 * 2 可以通过开辟新的线程为新生成的连接处理读写，但是一个进程开辟的线程是有限的，约为2048个线程，在Linux环境可以通过unlimit增加一个进程开辟的线程数，但是线程过多也会导致切换消耗的时间片较多。
 * 3 该服务器和客户端为应答式，实际场景为全双工通信模式，发送和接收要独立分开。
 * 4 该服务器和客户端未考虑粘包处理。
 *
 * 综上所述，是我们这个服务器和客户端存在的问题，为解决上述问题，我们在接下里的文章里做不断完善和改进，主要以异步读写改进上述方案。
 * 当然同步读写的方式也有其优点，比如客户端连接数不多，而且服务器并发性不高的场景，可以使用同步读写的方式。使用同步读写能简化编码难度。
 */

using std::cout , std::cin, std::cerr, std::endl;
using std::string;

namespace asio = boost::asio;
namespace ip = boost::asio::ip;

using socket_ptr = std::shared_ptr<asio::ip::tcp::socket>;
std::set<std::shared_ptr<std::jthread>> threads;

const size_t MAX_SIZE = 1024;

void session(const socket_ptr& socket_p)
{
    std::cout << "Received a client: " << socket_p.get()->remote_endpoint().address().to_string() << std::endl;
    // 因为每次处理单个连接的时候, 如果是一个线程, 将会阻塞.
    // 所以这个session函数应该是跑在多线程中的.
    try
    {
        for (;;)
        {
            std::cout << "Accepted..." << std::endl;
            auto buffer_p = std::unique_ptr<char[]>(new char[MAX_SIZE] { 0 });

            // receive client's send.
            auto error_code { boost::system::error_code() };
            size_t bytes_received = socket_p->read_some(asio::buffer(buffer_p.get(), MAX_SIZE), error_code);

            std::cout << "Reading complete." << std::endl;

            if (error_code == boost::asio::error::eof)
            {
                std::cout << "EOF: connection closed by peer." << std::endl;
                break;
            }
            else if (error_code) { throw boost::system::system_error(error_code); }

            std::cout << "Received from: " << socket_p->remote_endpoint().address().to_string() << std::endl;
            std::cout << "Data: " << buffer_p.get() << std::endl;

            // Echo to client.
            boost::asio::write(*socket_p, asio::buffer(buffer_p.get(), bytes_received));
        }
    }
    catch (const boost::system::system_error& e)
    {
        std::cerr << e.code() << ": " << e.what() << std::endl;
    }
}

void server(asio::io_context& io_context, unsigned short port)
{
    auto acceptor { ip::tcp::acceptor(io_context, ip::tcp::endpoint(ip::tcp::v4(), port)) };
    for (;;)
    {
        socket_ptr socket_p { new asio::ip::tcp::socket(io_context) };
        acceptor.accept(*socket_p);
        std::cout << "Thread started." << std::endl;
        auto thread = std::make_shared<std::jthread>(&session, socket_p);
        threads.insert(thread);
    }
}

int main(int argc, char* argv[])
{
    try
    {
        auto io_context { asio::io_context() };
        std::cout << "Starting server..." << std::endl;
        server(io_context, 10120);
        for (auto& thread : threads)
        {
            thread->join();
            std::cout << "Thread joined." << std::endl;
        }
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}
